﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace DaveSexton.Labs
{
	[TemplatePart(Name = "PART_Labs", Type = typeof(Selector))]
	[TemplatePart(Name = "PART_Output", Type = typeof(RichTextBox))]
	public class WindowsLabHost : Control, ILabHost
	{
		#region Public Properties
		public static readonly DependencyProperty HasSourceCodeProperty = DependencyProperty.Register("HasSourceCode", typeof(bool), typeof(WindowsLabHost));
		public static readonly DependencyProperty SourceCodeProperty = DependencyProperty.Register("SourceCode", typeof(string), typeof(WindowsLabHost));
		public static readonly DependencyProperty HasSourceXamlProperty = DependencyProperty.Register("HasSourceXaml", typeof(bool), typeof(WindowsLabHost));
		public static readonly DependencyProperty SourceXamlProperty = DependencyProperty.Register("SourceXaml", typeof(string), typeof(WindowsLabHost));
		public static readonly DependencyProperty CanChangeLabProperty = DependencyProperty.Register("CanChangeLab", typeof(bool), typeof(WindowsLabHost), new FrameworkPropertyMetadata(true));
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Object is immutable.")]
		public static readonly DependencyPropertyKey LabsProperty = DependencyProperty.RegisterReadOnly("Labs", typeof(ObservableCollection<Lab>), typeof(WindowsLabHost), new FrameworkPropertyMetadata(new ObservableCollection<Lab>()));

		public bool HasSourceCode
		{
			get
			{
				object value = GetValue(HasSourceCodeProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(HasSourceCodeProperty, value);
			}
		}

		public string SourceCode
		{
			get
			{
				return (string) GetValue(SourceCodeProperty);
			}
			set
			{
				SetValue(SourceCodeProperty, value);
			}
		}

		public bool HasSourceXaml
		{
			get
			{
				object value = GetValue(HasSourceXamlProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(HasSourceXamlProperty, value);
			}
		}

		public string SourceXaml
		{
			get
			{
				return (string) GetValue(SourceXamlProperty);
			}
			set
			{
				SetValue(SourceXamlProperty, value);
			}
		}

		public bool CanChangeLab
		{
			get
			{
				object value = GetValue(CanChangeLabProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(CanChangeLabProperty, value);
			}
		}

		public ObservableCollection<Lab> Labs
		{
			get
			{
				Contract.Ensures(Contract.Result<ObservableCollection<Lab>>() != null);

				var labs = (ObservableCollection<Lab>) GetValue(LabsProperty.DependencyProperty);

				Contract.Assume(labs != null);

				return labs;
			}
		}

		public string ApplicationTitle
		{
			get;
			set;
		}
		#endregion

		#region Private / Protected
		private static readonly Brush yellowish = new SolidColorBrush(Color.FromRgb(0xB3, 0xA3, 0x00));
		private static readonly Brush limeish = new SolidColorBrush(Color.FromRgb(0x00, 0xC6, 0x00));

		private Lab defaultLab;
		private bool loaded;
		private RichTextBox outputTextBox;
		private Selector labsSelector;
		#endregion

		#region Constructors
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline",
			Justification = "All static fields are being initialized inline.  The static constructor is for overriding metadata only.")]
		static WindowsLabHost()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowsLabHost), new FrameworkPropertyMetadata(typeof(WindowsLabHost)));
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WindowsLabHost" /> class.
		/// </summary>
		public WindowsLabHost()
		{
			Loaded += LabHost_Loaded;
		}
		#endregion

		#region Methods
		public void LoadLabs(LabCategory category)
		{
			var labs = Labs;

			labs.Clear();

			foreach (var lab in category.LabsRecursive.OrderBy(lab => lab.OrganizedName))
			{
				Contract.Assume(lab != null);

				var labToAdd = lab.IsProxy ? lab : WindowsConsoleLabHost.Create(lab);

				Contract.Assume(labToAdd.IsProxy);

				var actual = labToAdd.ActualLab as WindowsLab;

				if (actual != null)
				{
					actual.Host = this;
				}

				labs.Add(labToAdd);
			}
		}

		public void SetCurrentLab(Lab lab)
		{
			if (!lab.IsProxy)
			{
				lab = Labs.FirstOrDefault(
					proxy =>
					{
						var host = proxy.ActualLab as WindowsConsoleLabHost;

						return host != null && host.HostedLab == lab;
					});
			}

			if (loaded && labsSelector != null)
			{
				Dispatcher.BeginInvoke((Action) (() => labsSelector.SelectedItem = lab));
			}
			else
			{
				defaultLab = lab;
			}
		}

		public void Write(string message, TraceEventType eventType)
		{
			if (outputTextBox != null)
			{
				Dispatcher.BeginInvoke((Action) (() =>
				{
					WriteInternal(message, eventType);

					outputTextBox.ScrollToEnd();
				}));
			}
		}

		public void WriteLine(string message, TraceEventType eventType)
		{
			if (outputTextBox != null)
			{
				Dispatcher.BeginInvoke((Action) (() =>
				{
					WriteInternal(message, eventType, newLine: true);

					outputTextBox.ScrollToEnd();
				}));
			}
		}

		private void WriteInternal(string message, TraceEventType eventType, bool newLine = false)
		{
			Contract.Assume(outputTextBox != null);
			Contract.Assume(outputTextBox.Document != null);

			var paragraph = (Paragraph) outputTextBox.Document.Blocks.LastBlock;

			var foreground = GetForegroundBrush(eventType);

			var run = paragraph.Inlines.LastInline as Run;

			if (run != null && run.Foreground == foreground)
			{
				run.Text += message;
			}
			else
			{
				run = new Run(message ?? string.Empty);

				if (foreground != null)
				{
					run.Foreground = foreground;
				}

				paragraph.Inlines.Add(run);
			}

			if (newLine)
			{
				paragraph.Inlines.Add(new LineBreak());
			}
		}

		protected virtual Brush GetForegroundBrush(TraceEventType eventType)
		{
			switch (eventType)
			{
				case TraceEventType.Critical:
				case TraceEventType.Error:
					return Brushes.Red;
				case TraceEventType.Warning:
					return yellowish;
				case TraceEventType.Start:
				case TraceEventType.Resume:
					return limeish;
				case TraceEventType.Stop:
				case TraceEventType.Suspend:
					return Brushes.Magenta;
				case TraceEventType.Transfer:
					return Brushes.DarkCyan;
				case TraceEventType.Verbose:
					return Brushes.DarkGray;
				default:
					return null;
			}
		}

		public void Clear()
		{
			if (outputTextBox != null)
			{
				Dispatcher.BeginInvoke((Action) (() => outputTextBox.Document = new FlowDocument()
					{
						Blocks =
						{
							new Paragraph()
						}
					}));
			}
		}
		#endregion

		#region Event Handlers
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			outputTextBox = (RichTextBox) GetTemplateChild("PART_Output");
			labsSelector = (Selector) GetTemplateChild("PART_Labs");

			if (labsSelector != null)
			{
				labsSelector.SelectionChanged += LabsSelector_SelectionChanged;
			}

			if (outputTextBox != null)
			{
				Contract.Assume(CommandBindings != null);

				CommandBindings.Add(new CommandBinding(
					ApplicationCommands.Delete,
					(_, args) =>
					{
						Clear();

						args.Handled = true;
					},
					(_, args) =>
					{
						var paragraph = (Paragraph) outputTextBox.Document.Blocks.LastBlock;

						args.CanExecute = paragraph.Inlines.Count > 1 || (paragraph.Inlines.Count == 1 && ((Run) paragraph.Inlines.FirstInline).Text.Length > 0);
						args.Handled = true;
					}));
			}
		}

		private void LabHost_Loaded(object sender, RoutedEventArgs e)
		{
			if (labsSelector != null)
			{
				labsSelector.SelectedItem = defaultLab;
			}

			loaded = true;
		}

		private void LabsSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			Contract.Assume(labsSelector != null);

			var lab = (Lab) labsSelector.SelectedItem;

			bool hasSourceCode = lab != null && !string.IsNullOrWhiteSpace(lab.SourceCode);

			HasSourceCode = hasSourceCode;
			SourceCode = hasSourceCode ? lab.SourceCode : null;

			bool hasSourceXaml = lab != null && !string.IsNullOrWhiteSpace(lab.SourceXaml);

			HasSourceXaml = hasSourceXaml;
			SourceXaml = hasSourceXaml ? lab.SourceXaml : null;
		}
		#endregion
	}
}